package com.chatplus.application.common.util;

import org.apache.commons.lang3.StringUtils;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 实现C#的PasswordHasher密码处理类，便后续旧密码处理
 */
public class PasswordHasher {

    private final static int pbkdf2IterCountV3 = 10000;//循环次数
    private final static int pbkdf2IterCountV2 = 1000;

    private final static int pbkdf2SubkeyLength = 32;

    private final static int saltSize = 16;

    /**
     * C#密码hash算法V2
     * 加密方式主要是HMAC-SHA1方式，使用随机盐处理,
     * 128-bit 随机盐（salt）, 256-bit 密文（subkey）, 1000次迭代.
     * 结果格式：{ 0x00, salt, subkey }
     *
     * @param password
     * @return
     * @throws Exception
     */
    public static String hashPasswordV2(String password) throws Exception {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        byte[] salt = new byte[saltSize];
        random.nextBytes(salt);
        byte[] subkey;
        byte[] outputBytes = new byte[1 + saltSize + pbkdf2SubkeyLength];
        try {
            Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(password, salt, pbkdf2IterCountV2);
            subkey = rfc.getBytes(pbkdf2SubkeyLength);
            outputBytes[0] = 0x00;
            System.arraycopy(salt, 0, outputBytes, 1, salt.length);
            System.arraycopy(subkey, 0, outputBytes, 1 + saltSize, pbkdf2SubkeyLength);
            return Base64.getEncoder().encodeToString(outputBytes);
        } catch (Exception ex) {
            throw ex; // TODO FIXME ??
        }
    }

    /**
     * C#密码hash算法V3
     * 加密方式主要用HMAC-SHA256，使用随机盐处理
     * 128-bit 随机盐（salt）, 256-bit 密文（subkey）, 10000次迭代.
     * 结果格式：{ 0x01,prf (UInt32，prf固定值1)，iter count (UInt32,迭代次数),salt length (UInt32,盐的长度), salt, subkey }
     *
     * @param password
     * @return
     * @throws Exception
     */
    public static String hashPasswordV3(String password) throws Exception {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        byte[] salt = new byte[saltSize];
        random.nextBytes(salt);
        byte[] subkey;
        try {
            HMACSHA256 rfc = new HMACSHA256(password, salt, pbkdf2IterCountV3);
            subkey = rfc.getBytes(pbkdf2SubkeyLength);
            byte[] outputBytes = new byte[13 + salt.length + subkey.length];
            outputBytes[0] = 0x01;
            WriteNetworkByteOrder(outputBytes, 1, 1);
            WriteNetworkByteOrder(outputBytes, 5, pbkdf2IterCountV3);
            WriteNetworkByteOrder(outputBytes, 9, saltSize);
            System.arraycopy(salt, 0, outputBytes, 13, salt.length);
            System.arraycopy(subkey, 0, outputBytes, 13 + salt.length, subkey.length);
            return Base64.getEncoder().encodeToString(outputBytes);
        } catch (Exception ex) {
            throw ex;
        }
    }

    /**
     * 密码验证
     *
     * @param hashedPassword   加密后的密码
     * @param providedPassword 原始密码
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws UnsupportedEncodingException
     */
    public static boolean passwordVerificationResult(String hashedPassword, String providedPassword) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        if (StringUtils.isBlank(providedPassword) || StringUtils.isBlank(providedPassword)) {
            return false;
        }
        boolean passwordVerificationResult = false;
        byte[] decodedHashedPassword = Base64.getDecoder().decode(hashedPassword);
        switch (decodedHashedPassword[0]) {
            case 0x00:
                passwordVerificationResult = verifyHashedPasswordV2(decodedHashedPassword, providedPassword);
                break;
            case 0x01:
                passwordVerificationResult = verifyHashedPasswordV3(decodedHashedPassword, providedPassword);
                break;
            default:
                break;
        }
        return passwordVerificationResult;
    }

    private static boolean verifyHashedPasswordV2(byte[] hashedPassword, String password) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        if (hashedPassword.length != (1 + saltSize + pbkdf2SubkeyLength)) {
            return false;
        }
        byte[] salt = new byte[saltSize];
        System.arraycopy(hashedPassword, 1, salt, 0, salt.length);
        byte[] expectedSubkey = new byte[pbkdf2SubkeyLength];
        System.arraycopy(hashedPassword, 1 + salt.length, expectedSubkey, 0, expectedSubkey.length);
        Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(password, salt, pbkdf2IterCountV2);
        byte[] actualSubkey = rfc.getBytes(pbkdf2SubkeyLength);
        return Arrays.equals(expectedSubkey, actualSubkey);
    }

    private static boolean verifyHashedPasswordV3(byte[] hashedPassword, String password) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        int prf = readNetworkByteOrder(hashedPassword, 1);
        int iterCount = readNetworkByteOrder(hashedPassword, 5);
        int saltLength = readNetworkByteOrder(hashedPassword, 9);
        if (saltLength < 128 / 8) {
            return false;
        }
        byte[] salt = new byte[saltLength];
        System.arraycopy(hashedPassword, 13, salt, 0, salt.length);
        int subkeyLength = hashedPassword.length - 13 - salt.length;
        if (subkeyLength < 128 / 8) {
            return false;
        }
        byte[] expectedSubkey = new byte[subkeyLength];
        System.arraycopy(hashedPassword, 13 + salt.length, expectedSubkey, 0, expectedSubkey.length);
        HMACSHA256 rfc = new HMACSHA256(password, salt, iterCount);
        byte[] actualSubkey = rfc.getBytes(subkeyLength);
        return Arrays.equals(expectedSubkey, actualSubkey);
    }

    private static void WriteNetworkByteOrder(byte[] buffer, int offset, int value) {
        buffer[offset + 0] = (byte) (value >>> 24);
        buffer[offset + 1] = (byte) (value >>> 16);
        buffer[offset + 2] = (byte) (value >>> 8);
        buffer[offset + 3] = (byte) (value >>> 0);
    }

    private static int readNetworkByteOrder(byte[] buffer, int offset) {
        return ((buffer[offset + 0]) << 24)
            | ((buffer[offset + 1]) << 16)
            | ((buffer[offset + 2]) << 8)
            | ((buffer[offset + 3]));
    }

    public static void main(String[] args) {
        try {
            System.out.println(PasswordHasher.hashPasswordV3("444444"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
